<?php
/**
 *
 *
 *
 */

class Yz_String extends Yz_Type
{

    const FILTER_WORD = '~^\w+$~';

    // NOTE for our purposes, do not consider underscore as valid symbol in an identifier
    const FILTER_IDENTIFIER = '~^[a-z][0-9a-z]*$~i';

    protected static $expects = array(
        'test'                => 'string',
        'testNotEmpty'        => 'non-empty string',
        'testNotSpace'        => 'non-all-space string',
        'testWord'            => 'string of word characters',
        'testIdentifier'      => 'valid identifier',
    );


    /** valueOf(): reduce the value to string
    *       Objects which have toString() do not count
    *   @param scalar value
    *   @return string|null
    */
    public static function valueOf( $value )
    {
        switch( true ) {
            case is_bool( $value ): // before is_scalar
                $value = $value ? "true" : "false";
                break;
            case is_scalar($value): // "null" is not considered scalar
                $value = "$value";
                break;
            default:
                $value = null;
        } // switch

        return $value;
    } // valueOf


    /** ensure that a string's length is not 0
     *  @param string value
     *  @param string[null] context
     *  @return string
     *  @throw Yz_Exception_InvalidArgument
     *  @throw Yz_Exception_InvalidValueType
     */
    public static function testNotEmpty( $value, $context=null )
    {
        return static::doTest( $value, $context, 'testNotEmpty', function( $val ) {
            return $val !== '';
        } );
    }

    /** ensure that a string contains at least one non-space character
     *  @param string value
     *  @param string[null] context
     *  @return string
     *  @throw Yz_Exception_InvalidArgument
     *  @throw Yz_Exception_InvalidValueType
     */
    public static function testNotSpace( $value, $context=null )
    {
        return static::doTest( $value, $context, 'testNotSpace', function( $val ) {
            return trim($val) !== '';
        } );
    }

    /** ensure that a string consists of word characters only
     *  @param string value
     *  @param string[null] context
     *  @return string
     *  @throw Yz_Exception_InvalidArgument
     *  @throw Yz_Exception_InvalidValueType
     */
    public static function testWord( $value, $context=null )
    {
        return static::doTest( $value, $context, 'testWord', function( $val ) {
            return (bool)preg_match( Yz_String::FILTER_WORD, $val );
        } );
    }

    /** ensure that a string is a valid identifier in Yz terms
     *  @param string value
     *  @param string[null] context
     *  @return string
     *  @throw Yz_Exception_InvalidArgument
     *  @throw Yz_Exception_InvalidValueType
     */
    public static function testIdentifier( $value, $context=null )
    {
        return static::doTest( $value, $context, 'testIdentifier', function( $val ) {
            return (bool)preg_match( Yz_String::FILTER_IDENTIFIER, $val );
        } );
    }


    /** encode <>"'&\
     *   skip already encoded entities
     *   before replace, delete all non-printable chars (those before space - range 0x00:0x1F)
     *  @param string
     *  @return string
     */
    public static function toHTML( $value )
    {

        $value = static::valueOf( $value );

        debug( $value );

        if( null === $value || "" === $value ) {
            return "";
        }
        $regexp = <<<cc
~([<>"'\\\\])|(&(?!(#x[a-f0-9]{1,4})|(#\d{1,5})|([a-z]{1,6});))~i
cc;
        $src = preg_replace( '~[\x00-\x09\x0B-\x1F]~', ' ', $value );
        $dest = preg_replace_callback(
                $regexp,
                function( $matches ) { // PHP53 anonymous function!
                    $c = ord($matches[0]);
                    return "&#$c;";
                },
                $src );
        return $dest;
    } // toHTML



    /** re-convert string converted by toHTML back to plain text
     *  @param string
     *  @return string
     */
    public static function fromHTML($value)
    {
    }


    /** isTrue() : whether a string's value can be interpreted as boolean:
     *      yes/no, on/off, true/false, enabled/disabled
     *  @param string
     *  @return bool/null
     */
    public static function isTrue( $value )
    {
        // this converts booleans to {'true'|'false'}
        $value = static::valueOf( $value );

        if ( null === $value )
            return null;
/*
 * tests showed this 2x slower than switch
        $r = preg_match("~^(true|yes|on|enabled)|(false|no|off|disabled)$~i", $value, $m);
        if (! $r) {
            return null;
        }
        if (isset($m[2])) {
            return false;
        }
        return true;
*/
        // do not use mb_
        switch( strtolower($value) ) {
          case "on":
          case "true":
          case "enabled":
          case "yes":
            $r = true;
            break;
          case "off":
          case "false":
          case "disabled":
          case "no":
            $r = false;
            break;
          default:
            $r = $value;
        } // switch
        return $r;
    } // isTrue



    /** Replace (case-insensitive) variables %{var} in string
    *   @param string parameterized_string
    *   @param array-type vars
    *   @return string
    */
    public static function replaceVars( $str, $vars )
    {
        $str = Yz_String::test( $str, 'Parameterized string' );
        $vars = Yz_Hash::test($vars, 'replacement parameters');
        foreach ( $vars as $k => $v ) {
            if ("" === $str) {
                break;
            }
            if (null !== $v) {
                $v = Yz_String::test( $v, 'Parameter' );
            }
            $str = str_ireplace( '%{' .$k .'}', $v, $str );
        }

        return $str;
    } // replaceVars


    /** check whether the string is a valid identifier
     *  @param string
     *  @return bool
     */
    public static function isValidIdentifier( $str )
    {
        return (bool)preg_match( Yz_String::FILTER_IDENTIFIER, $str );
    }

    /** translate a-long-CaSe-iNseNsitive-string to aLongCaseInsensitiveString
     *  @param string
     *  @return string
     *  @throw InvalidValueType
     */
    public static function toCamelCase( $str )
    {
        $str = self::test($str);
        $str = preg_replace_callback(
            '~[\s._-]+([a-z]?)~',
            function($m) {
                if ($m[1]) {
                    return strtoupper($m[1]);
                }
                return '';
            },
            strtolower($str)
        );
        return $str;
    }

    /** translate aLongCaseSensitiveString to a-long-case-sensitive-string
     *  @param string
     *  @return string
     *  @throw InvalidValueType
     */
    public static function fromCamelCase( $str )
    {
        $str = self::test($str);
        $str = preg_replace_callback(
            '~[A-Z]~',
            function($m) {
                return '-' . strtolower($m[0]);
            },
            lcfirst($str) // ensure that the first char is not uppercase -> "-"
        );
        return $str;
    }



} // class

